/******************************************************************************
 * @file     vio_STM32L-Discovery.c
 * @brief    Virtual I/O implementation for board STM32L100C-Discovery
 * @version  V1.0.0
 * @date     24. August 2020
 ******************************************************************************/
/*
 * Copyright (c) 2020 Arm Limited (or its affiliates). All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*! \page vio_STM32L_Discovery Physical I/O Mapping

The table below lists the physical I/O mapping of this CMSIS-Driver VIO implementation.

Virtual Resource  | Variable       | Physical Resource on STM32L-Discovery          |
:-----------------|:---------------|:-----------------------------------------------|
vioBUTTON0        | vioSignalIn.0  | GPIO PA.0: Button USER                         |
vioLED0           | vioSignalOut.0 | GPIO PB.7: LD3 GREEN                           |
vioLED1           | vioSignalOut.1 | GPIO PB.6: LD4 BLUE                            |
*/

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "cmsis_vio.h"

#include "RTE_Components.h"
#include CMSIS_device_header

#if !defined CMSIS_VOUT || !defined CMSIS_VIN
#include "stm32l1xx_hal.h"
#if defined (RTE_DEVICE_FRAMEWORK_CUBE_MX)
#include "MX_Device.h"
#endif
#endif

// VIO input, output definitions
#define VIO_PRINT_MAX_SIZE      64U     // maximum size of print memory
#define VIO_PRINTMEM_NUM         4U     // number of print memories
#define VIO_VALUE_NUM            3U     // number of values
#define VIO_VALUEXYZ_NUM         3U     // number of XYZ values
#define VIO_IPV4_ADDRESS_NUM     2U     // number of IPv4 addresses
#define VIO_IPV6_ADDRESS_NUM     2U     // number of IPv6 addresses

// VIO input, output variables
__USED uint32_t      vioSignalIn;                                       // Memory for incoming signal
__USED uint32_t      vioSignalOut;                                      // Memory for outgoing signal
__USED char          vioPrintMem[VIO_PRINTMEM_NUM][VIO_PRINT_MAX_SIZE]; // Memory for the last value for each level
__USED int32_t       vioValue   [VIO_VALUE_NUM];                        // Memory for value used in vioGetValue/vioSetValue
__USED vioValueXYZ_t vioValueXYZ[VIO_VALUEXYZ_NUM];                     // Memory for XYZ value for 3-D vector
__USED vioAddrIPv4_t vioAddrIPv4[VIO_IPV4_ADDRESS_NUM];                 // Memory for IPv4 address value used in vioSetIPv4/vioGetIPv4
__USED vioAddrIPv6_t vioAddrIPv6[VIO_IPV6_ADDRESS_NUM];                 // Memory for IPv6 address value used in vioSetIPv6/vioGetIPv6

#if !defined CMSIS_VOUT
// Global types, variables, functions

typedef struct {
  GPIO_TypeDef *port;
  uint16_t      pin;
  GPIO_PinState active_state;
  uint8_t       reserved;
} PIN_CONFIG_t;

/* ------------------------------- Configuration ---------------------------- */
#define LED_NUM                         2U

#if defined (RTE_DEVICE_FRAMEWORK_CUBE_MX)
  #ifndef HAL_GPIO_MODULE_ENABLED
    #error "::Device:STM32Cube Framework (API): STM32CubeMX: Enable GPIO!"
  #endif

  #if (!defined(MX_PB7_Pin)) || (!defined(MX_PB6_Pin))
    #error "::Device:STM32Cube Framework (API): STM32CubeMX: Set pins PB7 and PB6 to GPIO_Output function!"
  #endif

  static const PIN_CONFIG_t LED_PIN_CONFIG[LED_NUM] = {
      { MX_PB7_GPIOx, MX_PB7_GPIO_Pin, GPIO_PIN_SET, 0U }
    , { MX_PB6_GPIOx, MX_PB6_GPIO_Pin, GPIO_PIN_SET, 0U }
  };
#else
  static const PIN_CONFIG_t LED_PIN_CONFIG[LED_NUM] = {
      { GPIOB,        GPIO_PIN_7,      GPIO_PIN_SET, 0U }
    , { GPIOB,        GPIO_PIN_6,      GPIO_PIN_SET, 0U }
  };
#endif
/* -------------------------------------------------------------------------- */

/**
  \fn          int32_t LED_Initialize (void)
  \brief       Initialize I/O interface for LEDs
  \returns
   - \b  0: function succeeded
   - \b -1: function failed
*/
int32_t LED_Initialize (void) {
#if !defined (RTE_DEVICE_FRAMEWORK_CUBE_MX)
  GPIO_InitTypeDef  gpioinitstruct;

  __HAL_RCC_GPIOB_CLK_ENABLE();

  gpioinitstruct.Mode   = GPIO_MODE_OUTPUT_PP;
  gpioinitstruct.Pull   = GPIO_NOPULL;
  gpioinitstruct.Speed  = GPIO_SPEED_FREQ_HIGH;

  gpioinitstruct.Pin    = LED_PIN_CONFIG[0].pin;
  HAL_GPIO_Init(LED_PIN_CONFIG[0].port, &gpioinitstruct);
  
  gpioinitstruct.Pin    = LED_PIN_CONFIG[1].pin;
  HAL_GPIO_Init(LED_PIN_CONFIG[1].port, &gpioinitstruct);
#else
  // All initialization code is generated by CubeMX
#endif

  return 0;
}

/**
  \fn          int32_t LED_On (uint32_t num)
  \brief       Turn on requested LED
  \param[in]   num  LED number
  \returns
   - \b  0: function succeeded
   - \b -1: function failed
*/
int32_t LED_On (uint32_t num) {

  if (num >= LED_NUM) {
    return -1;
  }

  if (LED_PIN_CONFIG[num].active_state == GPIO_PIN_RESET) {
    HAL_GPIO_WritePin(LED_PIN_CONFIG[num].port, LED_PIN_CONFIG[num].pin, GPIO_PIN_RESET);
  } else {
    HAL_GPIO_WritePin(LED_PIN_CONFIG[num].port, LED_PIN_CONFIG[num].pin, GPIO_PIN_SET);
  }

  return 0;
}

/**
  \fn          int32_t LED_Off (uint32_t num)
  \brief       Turn off requested LED
  \param[in]   num  LED number
  \returns
   - \b  0: function succeeded
   - \b -1: function failed
*/
int32_t LED_Off (uint32_t num) {

  if (num >= LED_NUM) {
    return -1;
  }

  if (LED_PIN_CONFIG[num].active_state == GPIO_PIN_RESET) {
    HAL_GPIO_WritePin(LED_PIN_CONFIG[num].port, LED_PIN_CONFIG[num].pin, GPIO_PIN_SET);
  } else {
    HAL_GPIO_WritePin(LED_PIN_CONFIG[num].port, LED_PIN_CONFIG[num].pin, GPIO_PIN_RESET);
  }

  return 0;
}
#endif

#if !defined CMSIS_VIN
// Add global user types, variables, functions here:

/* ------------------------------- Configuration ---------------------------- */
#define BUTTON_NUM                      1U

#if defined (RTE_DEVICE_FRAMEWORK_CUBE_MX)
  #ifndef HAL_GPIO_MODULE_ENABLED
    #error "::Device:STM32Cube Framework (API): STM32CubeMX: Enable GPIO!"
  #endif

  #ifndef MX_PA0_WKUP1_Pin
    #error "::Device:STM32Cube Framework (API): STM32CubeMX: Set pin PA0 to GPIO_Input function!"
  #endif

  static const PIN_CONFIG_t BUT_PIN_CONFIG[BUTTON_NUM] = {
      { MX_PA0_WKUP1_GPIOx, MX_PA0_WKUP1_GPIO_Pin, GPIO_PIN_SET, 0U }
  };
#else
  static const PIN_CONFIG_t BUT_PIN_CONFIG[BUTTON_NUM] = {
      { GPIOA,              GPIO_PIN_0,            GPIO_PIN_SET, 0U }
  };
#endif
/* -------------------------------------------------------------------------- */

/**
  \fn          int32_t Buttons_Initialize (void)
  \brief       Initialize I/O interface for buttons
  \returns
   - \b  0: function succeeded
   - \b -1: function failed
*/
int32_t Buttons_Initialize (void) {
#if !defined (RTE_DEVICE_FRAMEWORK_CUBE_MX)
  GPIO_InitTypeDef  gpioinitstruct;

  __HAL_RCC_GPIOA_CLK_ENABLE();

  gpioinitstruct.Mode   = GPIO_MODE_INPUT;
  gpioinitstruct.Pull   = GPIO_NOPULL;
  gpioinitstruct.Speed  = GPIO_SPEED_FREQ_HIGH;

  gpioinitstruct.Pin    = BUT_PIN_CONFIG[0].pin;
  HAL_GPIO_Init(BUT_PIN_CONFIG[0].port, &gpioinitstruct);
#else
  // All initialization code is generated by CubeMX
#endif
  return 0;
}

/**
  \fn          uint32_t Buttons_GetState (void)
  \brief       Get buttons state
  \returns     Buttons state
*/
uint32_t Buttons_GetState (void) {
  uint32_t val;
  uint8_t  i;

  val = 0U;
  for (i = 0U; i < BUTTON_NUM; i++) {
    if (HAL_GPIO_ReadPin(BUT_PIN_CONFIG[i].port, BUT_PIN_CONFIG[i].pin) == BUT_PIN_CONFIG[i].active_state) {
      val |= 1UL << i;
    }
  }

  return val;
}

#endif

// Initialize test input, output.
void vioInit (void) {
#if !defined CMSIS_VOUT
// Add user variables here:

#endif
#if !defined CMSIS_VIN
// Add user variables here:

#endif

  vioSignalIn  = 0U;
  vioSignalOut = 0U;

  memset(vioPrintMem, 0, sizeof(vioPrintMem));
  memset(vioValue,    0, sizeof(vioValue));
  memset(vioValueXYZ, 0, sizeof(vioValueXYZ));
  memset(vioAddrIPv4, 0, sizeof(vioAddrIPv4));
  memset(vioAddrIPv6, 0, sizeof(vioAddrIPv6));

#if !defined CMSIS_VOUT
  // Initialize LEDs pins
  LED_Initialize ();
#endif

#if !defined CMSIS_VIN
  // Initialize buttons pins (only USER button), MEMS pins
  Buttons_Initialize ();
#endif
}

// Print formated string to test terminal.
int32_t vioPrint (uint32_t level, const char *format, ...) {
  va_list args;
  int32_t ret;
#if !defined CMSIS_VOUT
// Add user variables here:

#endif

  if (level > vioLevelError) {
    return (-1);
  }

  if (level > VIO_PRINTMEM_NUM) {
    return (-1);
  }

  va_start(args, format);

  ret = vsnprintf((char *)vioPrintMem[level], sizeof(vioPrintMem[level]), format, args);

  va_end(args);

#if !defined CMSIS_VOUT
#endif

  return (ret);
}

// Set signal output.
void vioSetSignal (uint32_t mask, uint32_t signal) {
#if !defined CMSIS_VOUT
// Add user variables here:

#endif

  vioSignalOut &= ~mask;
  vioSignalOut |=  mask & signal;

#if !defined CMSIS_VOUT
  // Output signals to LEDs

  if (mask & vioLED0) {
    if (signal & vioLED0) {
      LED_On(0);
    } else {
      LED_Off(0);
    }
  }
  if (mask & vioLED1) {
    if (signal & vioLED1) {
      LED_On(1);
    } else {
      LED_Off(1);
    }
  }
#endif
}

// Get signal input.
uint32_t vioGetSignal (uint32_t mask) {
  uint32_t signal;
#if !defined CMSIS_VIN
// Add user variables here:

#endif

#if !defined CMSIS_VIN
  // Get input signals from buttons (only USER button)
  if (mask & vioBUTTON0) {
    if (Buttons_GetState() == 0U) {
      vioSignalIn &= ~vioBUTTON0;
    } else {
      vioSignalIn |=  vioBUTTON0;
    }
  }
#endif

  signal = vioSignalIn;

  return (signal & mask);
}

// Set value output.
void vioSetValue (uint32_t id, int32_t value) {
  uint32_t index = id;
#if !defined CMSIS_VOUT
// Add user variables here:

#endif

  if (index >= VIO_VALUE_NUM) {
    return;                             /* return in case of out-of-range index */
  }

  vioValue[index] = value;

#if !defined CMSIS_VOUT
// Add user code here:

#endif
}

// Get value input.
int32_t vioGetValue (uint32_t id) {
  uint32_t index = id;
  int32_t  value = 0;
#if !defined CMSIS_VIN
// Add user variables here:

#endif

  if (index >= VIO_VALUE_NUM) {
    return value;                       /* return default in case of out-of-range index */
  }

#if !defined CMSIS_VIN
// Add user code here:

//   vioValue[index] = ...;
#endif

  value = vioValue[index];

  return value;
}

// Set XYZ value output.
void vioSetXYZ (uint32_t id, vioValueXYZ_t valueXYZ) {
  uint32_t index = id;
#if !defined CMSIS_VOUT
// Add user variables here:

#endif

  if (index >= VIO_VALUEXYZ_NUM) {
    return;                             /* return in case of out-of-range index */
  }

  vioValueXYZ[index] = valueXYZ;

#if !defined CMSIS_VOUT
// Add user code here:

#endif
}

// Get XYZ value input.
vioValueXYZ_t vioGetXYZ (uint32_t id) {
  uint32_t index = id;
  vioValueXYZ_t valueXYZ = {0, 0, 0};
#if !defined CMSIS_VIN
// Add user variables here:

#endif

  if (index >= VIO_VALUEXYZ_NUM) {
    return valueXYZ;                    /* return default in case of out-of-range index */
  }

#if !defined CMSIS_VIN
// Add user code here:

//   vioValueXYZ[index] = ...;
#endif

  valueXYZ = vioValueXYZ[index];

  return valueXYZ;
}

// Set IPv4 address output.
void vioSetIPv4 (uint32_t id, vioAddrIPv4_t addrIPv4) {
  uint32_t index = id;
#if !defined CMSIS_VOUT
// Add user variables here:

#endif

  if (index >= VIO_IPV4_ADDRESS_NUM) {
    return;                             /* return in case of out-of-range index */
  }

  vioAddrIPv4[index] = addrIPv4;

#if !defined CMSIS_VOUT
// Add user code here:

#endif
}

// Get IPv4 address input.
vioAddrIPv4_t vioGetIPv4 (uint32_t id) {
  uint32_t index = id;
  vioAddrIPv4_t addrIPv4 = {0U, 0U, 0U, 0U};
#if !defined CMSIS_VIN
// Add user variables here:

#endif

  if (index >= VIO_IPV4_ADDRESS_NUM) {
    return addrIPv4;                    /* return default in case of out-of-range index */
  }

#if !defined CMSIS_VIN
// Add user code here:

//   vioAddrIPv4[index] = ...;
#endif

  addrIPv4 = vioAddrIPv4[index];

  return addrIPv4;
}

// Set IPv6 address output.
void vioSetIPv6 (uint32_t id, vioAddrIPv6_t addrIPv6) {
  uint32_t index = id;
#if !defined CMSIS_VOUT
// Add user variables here:

#endif

  if (index >= VIO_IPV6_ADDRESS_NUM) {
    return;                             /* return in case of out-of-range index */
  }

  vioAddrIPv6[index] = addrIPv6;

#if !defined CMSIS_VOUT
// Add user code here:

#endif
}

// Get IPv6 address input.
vioAddrIPv6_t vioGetIPv6 (uint32_t id) {
  uint32_t index = id;
  vioAddrIPv6_t addrIPv6 = {0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U,
                            0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U};
#if !defined CMSIS_VIN
// Add user variables here:

#endif

  if (index >= VIO_IPV6_ADDRESS_NUM) {
    return addrIPv6;                    /* return default in case of out-of-range index */
  }

#if !defined CMSIS_VIN
// Add user code here:

//   vioAddrIPv6[index] = ...;
#endif

  addrIPv6 = vioAddrIPv6[index];

  return addrIPv6;
}
